package algo3.algothief.modelo;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Random;
import java.io.File;
import java.io.IOException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;


public class Pais {
	
	private String direccionParaCargarAlPais;
	
	private String descripcionSobreElPais;
	
	private String nombreDeCapital;
	
	private Posicion posicionDeCapital;
	
	private ArrayList<String> objetosComunesRobables;
	private ArrayList<String> objetosValiososRobables;
	private ArrayList<String> objetosMuyValiososRobables;
	
	private HashMap<String,LugarDePais> lugares;
	
//-------------------------------------------------------------------------------------
	
	public Pais(String direccionDelPais) throws ParserConfigurationException, SAXException, IOException {
		this.direccionParaCargarAlPais = direccionDelPais;
		
		Document doc = this.cargarDocumentoNormalizado(direccionDelPais);
		this.cargarPais(doc);
	}
	
//-------------------------------------------------------------------------------------

	// La descripcion extra es para saber a que pais se refiere dentro del archivo.
	
	// La forma de serializar esta escrita lo mas generica posible.
	public Element serializar(Document doc, String descripcionExtra) {
		Element elementoPais = doc.createElement("Pais" + descripcionExtra);
		elementoPais.setAttribute("capital", nombreDeCapital);
		
		Element elementoDescripcionSobreElPais = doc.createElement("DescripcionSobreElPais" + descripcionExtra);
		elementoDescripcionSobreElPais.setAttribute("descripcion", descripcionSobreElPais);
		
		Element elementoDireccion = doc.createElement("DireccionDelPais");
		elementoDireccion.setAttribute("direccion", direccionParaCargarAlPais);
		
		elementoPais.appendChild(elementoDescripcionSobreElPais);
		elementoPais.appendChild(elementoDireccion);
		
		this.serializarLugares(elementoPais, doc);
		
		return elementoPais;
	}
	
	public static Pais hidratar(Document doc, String descripcionExtra) throws ParserConfigurationException, SAXException, IOException {
		Element elementoPais = (Element)doc.getElementsByTagName("Pais" + descripcionExtra).item(0);
		
		Element elementoDescripcionSobreElPais = (Element)elementoPais.getElementsByTagName("DescripcionSobreElPais" + descripcionExtra).item(0);
		String descripcionSobreElPais = elementoDescripcionSobreElPais.getAttribute("descripcion");
		
		Element elementoDireccion = (Element)elementoPais.getElementsByTagName("DireccionDelPais").item(0);
		String direccionDelPais = elementoDireccion.getAttribute("direccion");
		
		Pais paisNuevo = new Pais(direccionDelPais);
		paisNuevo.definirDescripcionSobreElPais(descripcionSobreElPais);
		
		ArrayList<String> nombresDeLugares = Pais.crearNombresDeLosPosiblesLugaresAVisitar();
		HashMap<String, LugarDePais> lugaresNuevos = Pais.hidratarLugares(doc, nombresDeLugares, paisNuevo.verNombreDeLaCapital());
		
		paisNuevo.definirPosiblesLugaresAVisitar(lugaresNuevos);
		
		return paisNuevo;
	}

//-------------------------------------------------------------------------------------
	
	public void cargarPais(Document doc) throws ParserConfigurationException, SAXException, IOException {
		Element elementoPais = (Element)doc.getElementsByTagName("Pais").item(0);
		this.nombreDeCapital = elementoPais.getAttribute("capital");
		
		Element elementoDescripcionSobreElPais = (Element)elementoPais.getElementsByTagName("DescripcionSobreElPais").item(0);
		this.descripcionSobreElPais = elementoDescripcionSobreElPais.getAttribute("descripcion");
		
		this.posicionDeCapital = Posicion.hidratar(doc);
		
		Element elementosObjetosComunes = (Element)elementoPais.getElementsByTagName("ObjetosComunesRobables").item(0);
		this.objetosComunesRobables = Pais.hidratarObjetosRobables(elementosObjetosComunes);
		
		Element elementosObjetosValiosos = (Element)elementoPais.getElementsByTagName("ObjetosValiososRobables").item(0);
		this.objetosValiososRobables = Pais.hidratarObjetosRobables(elementosObjetosValiosos);
		
		Element elementosObjetosMuyValiosos = (Element)elementoPais.getElementsByTagName("ObjetosMuyValiososRobables").item(0);
		this.objetosMuyValiososRobables = Pais.hidratarObjetosRobables(elementosObjetosMuyValiosos);
		
		ArrayList<String> nombresDeLugares = Pais.crearNombresDeLosPosiblesLugaresAVisitar();
		this.lugares = Pais.hidratarLugares(doc, nombresDeLugares, nombreDeCapital);
	}
	
//-------------------------------------------------------------------------------------

	private Document cargarDocumentoNormalizado(String direccionDelArchivo) throws ParserConfigurationException, SAXException, IOException {
		File archivo = new File(direccionDelArchivo);

		// Se traduce el archivo a algo manejable para el programa.
		DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
		DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
		Document doc = dBuilder.parse(archivo);
		doc.getDocumentElement().normalize();
		
		return doc;
	}
	
	
	
	private void serializarLugares(Element elementoPais, Document doc) {
		String nombreDeCapitalModificado = nombreDeCapital.replaceAll("\\s", "-");
		
		for (Entry<String, LugarDePais> lugar : lugares.entrySet()) {
			String nombreDelLugar = lugar.getKey();
			LugarDePais lugarDePais = lugar.getValue();
			
			String descripcionDelLugar = "-" + nombreDeCapitalModificado + "-" + nombreDelLugar;
			
			Element elementoLugar = lugarDePais.serializar(doc, descripcionDelLugar);
		    
		    elementoPais.appendChild(elementoLugar);
		}
	}
	
	
	
	private static ArrayList<String> crearNombresDeLosPosiblesLugaresAVisitar() {
		ArrayList<String> nombresDeLosPosiblesLugares = new ArrayList<String>();
		
		nombresDeLosPosiblesLugares.add("Aeropuerto");
		nombresDeLosPosiblesLugares.add("Banco");
		nombresDeLosPosiblesLugares.add("Biblioteca");
		
		return nombresDeLosPosiblesLugares;
	}
	
	private static HashMap<String, LugarDePais> hidratarLugares(Document doc, ArrayList<String> nombresDeLugares, String nombreDeCapital) {
		HashMap<String, LugarDePais> lugaresNuevos = new HashMap<String, LugarDePais>();
		
		String nombreDeCapitalModificado = nombreDeCapital.replaceAll("\\s", "-");
		
		for (String nombreDelLugar : nombresDeLugares) {
			String descripcionDelLugar = "-" + nombreDeCapitalModificado + "-" + nombreDelLugar;
			
			LugarDePais lugarDePais = LugarDePais.hidratar(doc, descripcionDelLugar);
		    
			lugaresNuevos.put(nombreDelLugar, lugarDePais);
		}
		
		return lugaresNuevos;
	}
	
	private void definirPosiblesLugaresAVisitar(HashMap<String, LugarDePais> lugaresNuevos) {
		this.lugares = lugaresNuevos;
	}
	
	
	
	private void definirDescripcionSobreElPais(String descripcion) {
		this.descripcionSobreElPais = descripcion;
	}
	
	private static ArrayList<String> hidratarObjetosRobables(Element elementosObjetosRobables) {
		ArrayList<String> objetosRobables = new ArrayList<String>();
		
		Element elementoObjetosRobables = (Element)elementosObjetosRobables.getChildNodes(); 
		
		NodeList elementosObjetos = elementoObjetosRobables.getElementsByTagName("Objeto");
		int cantidadDeObjetos = elementosObjetos.getLength();
		
		for (int i = 0; i < cantidadDeObjetos; i++) {
			String nombreDelObjeto = ((Element)elementosObjetos.item(i)).getAttribute("nombre");
			objetosRobables.add(nombreDelObjeto);
		}
		
		return objetosRobables;
	}
	
	
	
	private HashMap<String, Pista> hidratarPistasParaOtroPais() throws ParserConfigurationException, SAXException, IOException{
		Document doc = this.cargarDocumentoNormalizado(direccionParaCargarAlPais);
		
		HashMap<String, Pista> pistasDeLosLugares = new HashMap<String, Pista>();
		
		String nombreDeCapitalModificado = nombreDeCapital.replaceAll("\\s", "-");
		
		ArrayList<String> nombresDeLugares = Pais.crearNombresDeLosPosiblesLugaresAVisitar();
		for (String nombreDelLugar : nombresDeLugares) {
			String descripcionExtra = "ParaElProximoPais";
			String descripcionDelLugar = descripcionExtra + "-" + nombreDeCapitalModificado + "-" + nombreDelLugar;
			
			Pista pistaDelLugar = Pista.hidratar(doc, descripcionDelLugar);
		    
			pistasDeLosLugares.put(nombreDelLugar, pistaDelLugar);
		}
		
		return pistasDeLosLugares;
	}
	
	
	
	private ArrayList<EsconditeDeLadron> crearPosiblesEsconditesFalsosParaElLadron() {
		ArrayList<EsconditeDeLadron> posiblesEscondites = new ArrayList<EsconditeDeLadron>();
		
		EsconditeDeLadron ladronQueAcuchilla = new LugarConLadronQueAcuchilla();
		EsconditeDeLadron ladronQueDispara = new LugarConLadronQueDispara();
		
		posiblesEscondites.add(ladronQueAcuchilla);
		posiblesEscondites.add(ladronQueDispara);
		
		return posiblesEscondites;
	}
	
	private int generarEnteroAlAzarEntre(int minimoValor, int maximoValor) {
		Random generadorAleatorio = new Random();
		int numeroAlAzar = generadorAleatorio.nextInt(maximoValor - minimoValor + 1) + minimoValor;
	
		return numeroAlAzar;
	}
	
	private void modificarLosLugaresParaSerPotencialesEsconditesDelLadron() {
		ArrayList<EsconditeDeLadron> posiblesEscondites = this.crearPosiblesEsconditesFalsosParaElLadron();
		int cantidadDePosiblesEscondites = posiblesEscondites.size();
		
		for (Entry<String, LugarDePais> lugar : lugares.entrySet()) {
			String nombreDelLugar = lugar.getKey();
			
			int indiceDelEscondite = this.generarEnteroAlAzarEntre(0, cantidadDePosiblesEscondites-1);
		    EsconditeDeLadron potencialEscondite = posiblesEscondites.get(indiceDelEscondite);
		    
		    lugares.put(nombreDelLugar, potencialEscondite);
		}
	}
	
	private void esconderAlLadronEnUnLugarAlAzar() {
		String[] nombresDeLosLugares = lugares.keySet().toArray(new String[0]);
		
		int cantidadDeLugares = lugares.size();
		int indiceDelLugarAlAzar = (int)( Math.random() * cantidadDeLugares );
		
		String nombreDelLugarAlAzar = nombresDeLosLugares[indiceDelLugarAlAzar];
		
		EsconditeDeLadron esconditeRealDelLadron = new LugarConLadronEscondido();
		lugares.put(nombreDelLugarAlAzar, esconditeRealDelLadron);
	}
	
	private HashMap<Integer, String> crearEnterosPorLugar() {
		HashMap<Integer, String> numerosPorLugar = new HashMap<Integer, String>();
		
		Integer i = 0;
		for (Entry<String, LugarDePais> lugar : lugares.entrySet()) {
			String nombreDelLugar = lugar.getKey();
			numerosPorLugar.put(i, nombreDelLugar);
			i++;
		}
		
		return numerosPorLugar;
	}
	
//-------------------------------------------------------------------------------------
	
	public String verDescripcionSobreElPais() {
		return this.descripcionSobreElPais;
	}
	
	
	public String verNombreDeLaCapital(){
		return this.nombreDeCapital;
	}
	
	public Posicion verPosicion() {
		return this.posicionDeCapital;
	}
	
	
	public HashMap<String, LugarDePais> verLosPosiblesLugaresQueSePuedenVisitar() {
		return lugares;
	}
	
	
	public HashMap<String, Pista> verPistasDeLosLugaresDelPaisSobreEstePais() throws ParserConfigurationException, SAXException, IOException {
		HashMap<String, Pista> pistasDeLosLugaresDelPais = this.hidratarPistasParaOtroPais();
		return pistasDeLosLugaresDelPais;
	}
	
	
	public double calcularDistancia(Pais otroPais) {
		Posicion posicionDeOtraCapital = otroPais.verPosicion();
		return posicionDeCapital.distancia(posicionDeOtraCapital);
	}
	
	public void modificarPistasParaApuntarAlProximoPais(Pais otroPais) throws ParserConfigurationException, SAXException, IOException {
		HashMap<String, Pista> pistasDeLosLugaresDelOtroPais = otroPais.verPistasDeLosLugaresDelPaisSobreEstePais();
		for (Entry<String, Pista> pista : pistasDeLosLugaresDelOtroPais.entrySet()) {
			String nombreDelLugar = pista.getKey();
			Pista pistaNueva = pista.getValue();
			
			LugarDePais lugar = lugares.get(nombreDelLugar);
			lugar.modificarPista(pistaNueva);
		}
	}
	
	
	public String obtenerUnNombreDeObjetoSegunSuCalidad(CalidadComun calidad) {
		int cantidadDeNombresDeObjetos = objetosComunesRobables.size();
		
		Random generadorAleatorio = new Random();
		int numeroAlAzar = generadorAleatorio.nextInt(cantidadDeNombresDeObjetos);
		
		String nombreDelObjeto = objetosComunesRobables.get(numeroAlAzar);
		
		return nombreDelObjeto;
	}
	
	public String obtenerUnNombreDeObjetoSegunSuCalidad(CalidadValioso calidad) {
		int cantidadDeNombresDeObjetos = objetosValiososRobables.size();
		
		Random generadorAleatorio = new Random();
		int numeroAlAzar = generadorAleatorio.nextInt(cantidadDeNombresDeObjetos);
		
		String nombreDelObjeto = objetosValiososRobables.get(numeroAlAzar);
		
		return nombreDelObjeto;
	}
	
	public String obtenerUnNombreDeObjetoSegunSuCalidad(CalidadMuyValioso calidad) {
		int cantidadDeNombresDeObjetos = objetosMuyValiososRobables.size();
		
		Random generadorAleatorio = new Random();
		int numeroAlAzar = generadorAleatorio.nextInt(cantidadDeNombresDeObjetos);
		
		String nombreDelObjeto = objetosMuyValiososRobables.get(numeroAlAzar);
		
		return nombreDelObjeto;
	}
	
	
	public void esconderLadronEnAlgunLugar() {
		this.modificarLosLugaresParaSerPotencialesEsconditesDelLadron();
		this.esconderAlLadronEnUnLugarAlAzar();
	}
	
	
	public void restablecerPistas() throws ParserConfigurationException, SAXException, IOException {
		Document doc = this.cargarDocumentoNormalizado(direccionParaCargarAlPais);
		
		ArrayList<String> nombresDeLugares = Pais.crearNombresDeLosPosiblesLugaresAVisitar();
		this.lugares = Pais.hidratarLugares(doc, nombresDeLugares, nombreDeCapital);
	}

	public void agregarPistaSobreLadron(String pista) {
		int cantidadDeLugares = lugares.size();
		Integer numeroAlAzar = this.generarEnteroAlAzarEntre(0,cantidadDeLugares - 1);
		
		HashMap<Integer, String> enterosPorLugar = this.crearEnterosPorLugar();
		String lugarAlAzar = enterosPorLugar.get(numeroAlAzar); 
		
		Pista pistaDelLugarAlAzar = lugares.get(lugarAlAzar).verPista();
		pistaDelLugarAlAzar.agregarPistaDeLadron(pista);
	}
}
